package com.kylin.domain.oauth2.social.wechat.connect;

import com.kylin.domain.oauth2.social.WechatCacheManager;
import com.kylin.domain.oauth2.social.jwt.JwtTokenProvider;
import com.kylin.domain.oauth2.social.wechat.EncryptedUserData;
import com.kylin.domain.oauth2.social.wechat.WechatInterpreter;
import com.kylin.domain.oauth2.social.wechat.WechatProperties;
import com.kylin.domain.oauth2.social.wechat.WechatSession;
import com.kylin.domain.user.command.CreateWechatUserCommand;
import com.kylin.domain.user.command.UpdateUserPhoneCommand;
import com.kylin.domain.user.command.UpdateWechatUserCommand;
import com.kylin.domain.user.provider.WechatUserProvider;
import com.kylin.domain.user.vo.WechatUserInfoVO;
import com.kylin.shared.common.consts.Constants;
import com.kylin.shared.enums.GenderEnum;
import com.kylin.shared.enums.ResultCodeEnum;
import com.kylin.shared.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 微信小程序服务提供商，OAuth2流程处理器的提供器
 * @author Damon S.
 * @description
 * @date 2019/1/4 0004 10:02
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class WechatServiceProvider {

    private final WechatProperties wxProperties;
    private final WechatInterpreter wxInterpreter;
    private final WechatUserProvider wxUserProvider;
    private final WechatOAuth2Template wxOAuth2Template;
    private final JwtTokenProvider jwtTokenProvider;
    private final WechatCacheManager wxCacheManager;


    /** 微信登录 */
    @Transactional(rollbackFor = Exception.class)
    public WechatUserInfoVO wxLogin(String code) {
        // 获取 Session
        WechatSession session = wxOAuth2Template.getSession(
                code, wxProperties
        );
        Assert.isTrue(session.isValid(), session.getErrMsg());

        // 获取用户唯一标识符 openid 成功，根据 OpenId 查找或创建用户
        WechatUserInfoVO userInfo = findUserInfoByOpenId(session.getOpenId());
        if (WechatUserInfoVO.EMPTY.equals(userInfo)) {
            throw new BusinessException(ResultCodeEnum.USER_NOT_EXIST);
        }
        // 生成 token 放入返回对象中
        userInfo.setToken(jwtTokenProvider.createToken(
                Collections.emptyMap(),
                String.valueOf(session.getOpenId())
        ));
        // 缓存对应关系
        wxCacheManager.cacheLoginInfo(userInfo, session);
        return userInfo;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean updateUserInfo(EncryptedUserData data) {
        WechatUserInfoVO userInfo = this.parseUserInfo(data);
        if (WechatUserInfoVO.EMPTY == userInfo) {
            return false;
        }
        // 然后更新用户信息
        Integer affectedRows = wxUserProvider.updateUserInfo(
                UpdateWechatUserCommand.builder()
                        .city(userInfo.getCity())
                        .country(userInfo.getCountry())
                        .gender(GenderEnum.parse(userInfo.getGender()))
                        .language(userInfo.getLanguage())
                        .nickName(userInfo.getNickName())
                        .province(userInfo.getProvince())
                        .avatarUrl(userInfo.getAvatarUrl())
                        .userId(Long.parseLong(data.getUserId()))
                        .updatedBy(Constants.USER_ADMIN)
                        .updatedAt(LocalDateTime.now())
                        .build()
        );
        // 判断更新是否成功
        boolean success = Objects.equals(affectedRows, Constants.INT_ONE);
        Assert.isTrue(success, "此用户不存在或存在多于一条用户数据,UserId=" + data.getUserId());
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    public WechatUserInfoVO updatePhoneNo(EncryptedUserData data) {
        WechatUserInfoVO userInfo = this.parseUserInfo(data);
        if (WechatUserInfoVO.EMPTY == userInfo) {
            return WechatUserInfoVO.EMPTY;
        }
        // 然后更新手机信息
        Integer affectedRows = wxUserProvider.updatePhoneNo(
                UpdateUserPhoneCommand.builder()
                        .phoneNumber(userInfo.getPhoneNumber())
                        .userId(Long.parseLong(userInfo.getUserId()))
                        .updatedBy(Constants.USER_ADMIN)
                        .updatedAt(LocalDateTime.now())
                        .build()
        );
        // 判断更新是否成功
        boolean success = Objects.equals(affectedRows, Constants.INT_ONE);
        Assert.isTrue(success, "此用户不存在或存在多于一条用户数据,UserId=" + data.getUserId());
        return userInfo;
    }

    private WechatUserInfoVO parseUserInfo(EncryptedUserData data) {
        // 先从缓存中获取 session key
        Optional.ofNullable(wxCacheManager.get(Constants.KEY_USERID_SESSION_KEY + data.getUserId()))
                .ifPresent(data::setSessionKey);
        if (StringUtils.isEmpty(data.getSessionKey())) {
            log.error("获取SessionKey失败，无法解析用户数据。UserId={}", data.getUserId());
            return WechatUserInfoVO.EMPTY;
        }
        WechatUserInfoVO userInfo = wxInterpreter.getUserInfo(data);
        if (WechatUserInfoVO.EMPTY == userInfo) {
            log.error("解析用户信息失败。UserId={}", data.getUserId());
            return WechatUserInfoVO.EMPTY;
        }
        userInfo.setUserId(data.getUserId());
        return userInfo;
    }

    /**
     * 从缓存获取用户信息
     * @param openId 微信返回的openId
     * @return 获取到的用户信息
     */
    @NotNull
    public WechatUserInfoVO findUserInfoByOpenId(String openId) {
        AtomicReference<WechatUserInfoVO> userInfo =
                new AtomicReference<>(WechatUserInfoVO.EMPTY);
        Optional.ofNullable(
                wxCacheManager.get(Constants.KEY_OPENID_USERINFO + openId)
        ).ifPresentOrElse(
                userRaw -> userInfo.set(WechatUserInfoVO.parse(userRaw)),
                () -> userInfo.set(wxUserProvider.findOrCreate(
                        CreateWechatUserCommand.builder()
                                .createdBy(Constants.USER_ADMIN)
                                .createdAt(LocalDateTime.now())
                                .openId(openId).build()
                ))
        );
        return userInfo.get();
    }
}
